/**
 * Applefy.js (jQuery)
 * 
 * Experimental image slider/viewer jQuery extension.
 * <p>
 * JavaScript code for tab-based slider functionality and Apple-like theme taken
 * from http://tutorialzine.com/2009/11/beautiful-apple-gallery-slideshow/.
 * 
 * @version 0.1 (Experimental)
 * @author Tin
 * @dependency jQuery v1.7.1+
 */
(function($) {
	"use strict";

	// The unique global name-space used by this jQuery extension.
	var namespace = "applefy",

	// The following object encapsulates all of the functionality, event
	// handlers, constructors and utilities provided and used by this extension
	// both internally (private) and externally (publicly exposed API methods).
	methods = {

		// Binds handlers for touch (mouse-drag) functionality to the elements.
		_enableTouch : function(slides, positions, settings) {
			var that, origin, start, move, end, begin, finish, displacement;
			slides.on("mousedown.".concat(namespace), ".slide", function(e) {
				that = $(this);
				start = e.pageX;
				begin = new Date().getTime();
				origin = slides.offset().left;
				slides.stop().addClass("mousedown");
				e.preventDefault(); // important: **know what this does**
			}).on("mousemove.".concat(namespace), ".slide", function(e) {
				var offset;
				if (that) {
					move = e.pageX;
					displacement = move - start;
					offset = origin + displacement;
					slides.offset({
						left : offset
					});
				}
			});
			// mouse-up bound to window so that it is always caught and handled
			$(window).on("mouseup.".concat(namespace), function(e) {
				var which, first, last, next, prev, margin, velocity, duration;
				if (that) {
					end = e.pageX;
					finish = new Date().getTime();
					margin = that.outerWidth() / 10;
					first = 0, last = slides.children().length - 1;
					next = start - (margin), prev = start + (margin);
					duration = finish - begin;
					velocity = displacement / duration;
					which = that.prevAll(".slide").length;
					if (which > first && prev < end) {
						which--;
					} else if (which < last && (next > end)) {
						which++;
					} else {
						// stay on the current slide
					}
					var totalDist, remaining, rate, actualRate, setRate;
					totalDist = $(slides.children().get(which)).outerWidth();
					remaining = totalDist - Math.abs(displacement);
					actualRate = (remaining / Math.abs(velocity));
					setRate = settings.transitionDuration;
					rate = (actualRate < setRate) ? actualRate : setRate;
					that = null;
					var tabs = settings.tabs, active;
					if (tabs) {
						active = $(tabs.get(which));
						tabs.removeClass("active").addClass("inactive");
						active.addClass("active").removeClass("inactive");
					}
					slides.stop().removeClass("mousedown").animate({
						left : -positions[which]
					}, rate, "swing", function() {
						// sliding transition done
					});
				}
			});
		},

		// Disables handlers related to touch functionality for given elements.
		_disableTouch : function(context, data) {
			// auto-generated method stub
		},

		// Setup and configuration routine for tab-based functionality, executed
		// whenever the 'enable tabs' method is invoked.
		_onEnableTabs : function(slides, tabs, positions, settings) {
			tabs.children("a").on("click.".concat(namespace), function(e) {
				var that, clickedTab, pos;
				that = $(this);
				clickedTab = that.parent();
				if (!clickedTab.hasClass("active")) {
					pos = that.parent().prevAll(".menuItem").length;
					tabs.removeClass("active").addClass("inactive");
					clickedTab.addClass("active").removeClass("inactive");
					slides.stop().animate({
						left : -positions[pos]
					}, settings.transitionDuration, function() {
						// sliding transition done
					});
				}
				e.preventDefault();
			});
		},

		// Binds handlers for tab-based functionality to the specified elements.
		_enableTabs : function(slides, positions, settings) {
			var tabs, active, current;
			tabs = settings.tabs;
			active = $(tabs.get(settings.activeSlide));
			current = tabs.filter(".active");
			methods._onEnableTabs(slides, tabs, positions, settings);
			if (!current.is(active)) {
				current.removeClass("active");
				active.addClass("active").siblings().addClass("inactive");
			}
		},

		// Disables handlers related to tab functionality for given elements.
		_disableTabs : function(context, data) {
			// auto-generated method stub
		},

		// Binds global data and events needed for auto-advance functionality.
		_enableAutoAdvance : function(context, settings) {
			var that, tabs, next, cycle, autoAdvance = function() {
				that = settings.tabs.filter(".active");
				next = that.prevAll(".menuItem").length + 1;
				if (that.length !== 1) {
					$.error("Cannot be more or less than one active tab");
					return false;
				} else if (next < 0) {
					return false;
				} else {
					tabs = settings.tabs.children("a");
					tabs.eq(next % (tabs.length)).trigger("click");
				}
			};
			if (settings.autoAdvance.waitDuration) {
				// Stored index for auto-advance, used to clear the interval:
				var autoAdvanceInterval = setInterval(function() {
					autoAdvance();
				}, settings.autoAdvance.waitDuration);
				context.data(namespace, {
					autoAdvanceInterval : autoAdvanceInterval
				});
			}
		},

		// Disables data and events related to auto-advance functionality.
		_disableAutoAdvance : function(data) {
			clearInterval(data.autoAdvanceInterval);
		},

		// Simple configuration facade that checks settings on initialization.
		_configure : function(context, data, settings) {
			var positions = data.positions;
			if (settings.tabs) {
				methods._enableTabs(context, positions, settings);
			}
			if (settings.touch) {
				methods._enableTouch(context, positions, settings);
			}
			if (settings.autoAdvance) {
				methods._enableAutoAdvance(context, settings);
			}
		},

		// Setup and configuration routine executed whenever the initialization
		// method is invoked and initialization is deemed to be required for the
		// specified element.
		_onInit : function($this, settings) {
			var data, maxHeight, maxWidth, totalLength, positions = new Array();
			maxHeight = maxWidth = totalLength = 0;
			// Evaluate each slide and aggregate data needed by the extension
			$this.children(".slide").each(function(pos) {
				var that, height, width;
				that = $(this);
				width = that.outerWidth(true);
				height = that.outerHeight(true);
				if (!(width && height)) {
					$.error("No height/width for Slide number " + pos);
					return false;
				}
				positions[pos] = totalLength;
				totalLength += width;
				if (height > maxHeight) {
					maxHeight = height;
				}
				return that;
			});
			// Configure the slides UI widget and store data on the DOM element
			$this.width(totalLength).height(maxHeight).data(namespace, {
				settings : settings,
				maxWidth : maxWidth,
				maxHeight : maxHeight,
				positions : positions,
				totalLength : totalLength
			}).css({
				position : "relative"
			}).stop().animate({
				left : -positions[settings.activeSlide]
			}, settings.transitionDuration);
			data = $this.data(namespace); // reference to plugin's new data
			// Check and configure optional functionality and settings
			methods._configure($this, data, settings);
		},

		// public API method that is called to initialize the plugin's data and
		// event handlers on each of the selected elements, overridden with the
		// provided optional settings.
		init : function(options) {
			var settings = $.extend({
				tabs : false,
				touch : false,
				activeSlide : 0,
				autoAdvance : false,
				transitionDuration : 400
			}, options);
			// Try to initialize extension on each element passed in for setup:
			return this.each(function() {
				var that, data;
				that = $(this);
				data = that.data(namespace);
				if (!data) {
					// Run initialization routine only if not already done
					methods._onInit(that, settings);
				}
			});
		},

		// public API method called to finalize and remove all data and events
		// related to the drag'n'drop plug-in.
		destroy : function() {
			return this.each(function() {
				var that = $(this), data = that.data(namespace);
				methods._disableTouch(that);
				methods._disableTabs(that, data);
				methods._disableAutoAdvance(data);
				$(window).unbind("." + namespace); // touch
				$("a").unbind("." + namespace); // tabs
				that.unbind("." + namespace); // all
				that.removeData(namespace);
			});
		}
	};

	// Drag'n'Drop jQuery extension, using jQuery style design pattern.
	$.fn.applefy = function(method) {
		var optionalArgs = Array.prototype.slice.call(arguments, 1);
		if (methods[method]) {
			if (typeof method === "string") {
				// Extend native string type with convenience method startsWith:
				if (typeof String.prototype.startsWith != "function") {
					String.prototype.startsWith = function(str) {
						return this.slice(0, str.length) === str;
					};
				}
				if (method.startsWith("_")) {
					// private member: not visible to public API (deny access)
					$.error("Error: the method " + method + " is not visible.");
					return false;
				}
			} else {
				// invalid argument: not a method name (i.e. not a string)
				$.error("Assertion Error: the argument is not a method name.");
				return false;
			}
			return methods[method].apply(this, optionalArgs);
		} else if (typeof method === "object" || !method) {
			return methods.init.apply(this, arguments);
		} else {
			$.error("Method " + method + " does not exist.");
		}
	};
})(jQuery);
